﻿using nhCore.db;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//所有命令在本文档，命令自带Analyzer，WeatherAnalyzer带Decoder

/******************************************************************************************
 * * 状态                   常驻     内部序号   失败处理               退出机制
 * 对应成员               Automatic  ResetCmd                           IsDelete
 * RealTimeCmd              *           -       SleepState              -
 * HistoryCmd               *           *       ResetCmd SleepState     -
 * CalibrationTimeCmd       *           -       SleepState              -
 * CalibrationTimeCmd       -           -       退出                    收到正确返回后
 * ConfigCmd                -           *       ResetCmd 退出           读出非法或最大数e
 *******************************************************************************************/
namespace nhCore.Modbus
{
    /// <summary>
    /// 本机为主机，向从机发送，命令类
    /// </summary>
    public abstract class CmdFrame : BaseFrame
    {
        protected int WorkDelayTime { get; set; }//命令间隔
        protected int SleepDelayTime { get; set; }// 休眠状态时命令间隔

        protected CmdFrame(byte address, byte code)
        {
            Address = address;
            Code = code;
        }

        /// <summary>
        /// key就地址加控制码，可重写
        /// </summary>
        public virtual string Key => Address.ToString("X") + "H、" + Code.ToString("X") + "H";

        /// <summary>
        /// 是否自动发送，程序开始是即发送
        /// </summary>
        public bool Automatic { get; set; }

        /// <summary>
        /// 继续发送下一条指令延时，实时为1s，历史为0，同步完成为60s
        /// </summary>
        public int DelayTime => SleepState ? SleepDelayTime : WorkDelayTime;

        /// <summary>
        /// 休眠状态
        /// </summary>
        public bool SleepState { get; set; }

        /// <summary>
        /// 重置参数状态，历史、采集仪配置
        /// </summary>
        protected bool Reset { get; set; }

        /// <summary>
        /// 历史数据偏移量
        /// </summary>
        public int OffSet { get; set; }

        /// <summary>
        /// 命令对应的分析器
        /// </summary>
        public Analyzer Analyzer { get; set; }

        /// <summary>
        /// 是否是响应的数据，效验通过，功能码、地址符合
        /// </summary>
        /// <param name = "byts" >数据帧</ param >
        /// < returns ></ returns >
        public virtual bool IsAnswer(object o)
        {
            bool result = false;
            if (o is byte[])
            {
                byte[] byts = o as byte[];
                FrameValid mb = new FrameValid(byts, true);
                if (mb.IsValid && mb.Code == Code && (mb.Address == Address || Address == 00))
                {
                    result = true;
                }
            }
            else
            {
                if (o != null)
                {
                    result = true;
                }
            }
            return result;
        }

        /// <summary>
        /// 获取命令
        /// </summary>
        /// <returns></returns>
        public abstract object GetCmd();

        /// <summary>
        /// 切换到下一条命令，Continue命令有的功能
        /// </summary>
        public virtual void NextCmd() { }

        /// <summary>
        /// 重置命令
        /// </summary>
        public virtual void ResetCmd() { }
    }

    public class RealTimeCmd : CmdFrame
    {
        /// <summary>
        /// 生成读数据帧
        /// </summary>
        /// <param name="address">地址</param>
        /// <param name="code">控制码</param>
        public RealTimeCmd(byte address, byte code = 0x04, int workDelayTime = 1 * 1000) : base(address, code)
        {
            Automatic = true;
            WorkDelayTime = workDelayTime;
            SleepDelayTime =  29 * 1000;//尽量和60秒的读历史数据错开

            if (workDelayTime > SleepDelayTime)
            {   //休眠间隔不大于工作间隔
                SleepDelayTime = workDelayTime;
            }

            Analyzer = new WeatherAnalyzer();
        }

        /// <summary>
        /// 通过List<DbConfig>生成实时命令实例
        /// </summary>
        /// <param name="configs">配置</param>
        /// <param name="code">控制码</param>
        /// <param name="workDelayTime">工作状态间隔毫秒</param>
        /// <param name="sleepDelayTime">休眠状态间隔毫秒</param>
        public RealTimeCmd(List<ElementConfig> configs, byte code = 0x04, int workDelayTime = 1000, int sleepDelayTime = 1000) : base(configs[0].DeviceAddress, code)
        {
            UInt16 maxReg = (UInt16)configs.Max(c => c.RegAddress);

            ElementConfig dbConfig = configs.Where(c => c.RegAddress == maxReg).First();
            maxReg += (ushort)(dbConfig.NDataType.RegCount - 1);

            Index = (UInt16)configs.Min(c => c.RegAddress);
            Len = (byte)(maxReg - Index + 1);

            WorkDelayTime = workDelayTime;
            SleepDelayTime = sleepDelayTime;//尽量和60秒的读历史数据错开

            Analyzer = new ReadRegAnalyzer();
            List<Decoder> decoders = new List<Decoder>();
            foreach (ElementConfig c in configs)
            {
                Decoder decoder = NDataType.GetDecoder(c);
                decoder.ReturnDataIndex = (UInt16)(c.RegAddress - Index);
                decoder.ElementConfig = c;
                decoders.Add(decoder);
            }
            Analyzer.Decoders = decoders;   
        }

        /// <summary>
        /// 读取多少寄存器，高8位无意义
        /// </summary>
        public byte Len { get; set; } = 27;

        /// <summary>
        /// 读取起始寄存器地址，默认为0
        /// </summary>
        public ushort Index { get; set; }

        /// <summary>
        /// 组帧，读实时数据命令
        /// </summary>
        /// <returns></returns>
        public override object GetCmd()
        {
            byte[] cmd = new byte[8] { Address, Code, (byte)(Index >> 8), (byte)Index, 0, Len, 0, 0 };
            AddCrc(ref cmd);
            return cmd;
        }
    }

    /// <summary>
    /// 读历史数据命令帮助类
    /// </summary>
    public class HistoryCmd : CmdFrame
    {
        public HistoryCmd(byte address, int sleepDelayTime = 60 * 1000) : base(address, 0x41)
        {
            Automatic = true;
            SleepDelayTime = sleepDelayTime;
            Analyzer = new WeatherAnalyzer();
        }

        /// <summary>
        /// 切换到下一条命令
        /// </summary>
        public override void NextCmd() { OffSet += 1; }

        /// <summary>
        /// 重置命令,从最近读历史数据
        /// </summary>
        public override void ResetCmd()
        {
            OffSet = -1;//复位后会执行NextCmd，回到0
        }

        /// <summary>
        /// 组帧，读历史数据命令
        /// </summary>
        /// <returns></returns>
        public override object GetCmd()
        {
            byte[] cmd = new byte[8] { Address, Code, (byte)(OffSet >> 24), (byte)(OffSet >> 16),
                (byte)(OffSet >> 8), (byte)OffSet, 0, 0 };
            AddCrc(ref cmd);
            return cmd;
        }
    }

    /// <summary>
    /// 读配置数据命令
    /// </summary>
    public class ConfigCmd : CmdFrame
    {
        public ConfigCmd(byte address) : base(address, 0x11)
        {
            Analyzer = new ConfigAnalyzer();
        }

        public override void NextCmd() { OffSet += 0x10; }

        /// <summary>
        /// 重置命令,从最近读配置数据
        /// </summary>
        public override void ResetCmd()
        {
            OffSet = -1;//复位后会执行NextCmd，回到0
        }

        /// <summary>
        ///  组帧，读配置数据命令
        /// </summary>
        /// <returns></returns>
        public override object GetCmd()
        {
            byte[] cmd = new byte[8] {Address,Code, (byte)((OffSet >> 8) + 0x10), (byte)OffSet,
            0,0x10,0,0};
            AddCrc(ref cmd);
            return cmd;
        }
    }

    /// <summary>
    /// 校时命令
    /// </summary>
    public class CalibrationTimeCmd : CmdFrame
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="address"></param>
        public CalibrationTimeCmd(byte address) : base(address, 0x10)
        {
            Automatic = true;
            WorkDelayTime = 1000;
            SleepDelayTime = 60 * 60 * 1000;
            Analyzer = new CalibrationTimeAnalyzer();
        }

        /// <summary>
        /// 组帧，校正时间
        /// </summary>
        /// <returns></returns>
        public override object GetCmd()
        {
            DateTime dt = DateTime.Now;
            byte[] cmd = new byte[15] {Address,Code,0,0,0,3,6,(byte)(dt.Year - 2000), (byte)dt.Month,
           (byte)dt.Day, (byte)dt.Hour,(byte)dt.Minute,(byte)dt.Second,0,0};
            AddCrc(ref cmd);
            return cmd;
        }
    }

    public class WriteRegCmd : CmdFrame
    {
        public WriteRegCmd(byte address, UInt16 regAddress, UInt16 regData) : base(address, 0x06)
        {
            RegAddress = regAddress;
            RegData = regData;
        }

        public UInt16 RegAddress { get; set; }
        private UInt16 RegData { get; set; }

        public override object GetCmd()
        {
            byte[] cmd = new byte[8] {Address,Code, (byte)(RegAddress >> 8), (byte)RegAddress,
                (byte)(RegData >> 8), (byte)RegData, 0,0};
            AddCrc(ref cmd);
            return cmd;
        }
    }

}
